package com.nlelpct.manage.config;

import com.nlelpct.manage.constant.SecurityConstant;
import com.nlelpct.manage.context.TransmittableThreadLocalSecurityContextHolderStrategy;
import com.nlelpct.manage.enums.ResultEnum;
import com.nlelpct.manage.filter.AccreditFilter;
import com.nlelpct.manage.filter.LoginFilter;
import com.nlelpct.manage.hanlder.TokenLogoutHandler;
import com.nlelpct.manage.model.vo.ResponseResult;
import com.nlelpct.manage.service.SecurityUserDetailsServiceImpl;
import com.nlelpct.manage.utils.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

/**
 * @author :小马
 * @date : 2023-07-12
 */
@Configuration
@EnableWebSecurity
@Slf4j
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfigurer {

    private final RedisTemplate<String, Object> redisTemplate;

    private final SecurityUserDetailsServiceImpl userDetailsService;

    @Autowired
    public WebSecurityConfigurer(RedisTemplate<String, Object> redisTemplate, SecurityUserDetailsServiceImpl userDetailsService) {
        this.redisTemplate = redisTemplate;
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public LoginFilter loginFilter(AuthenticationManager authenticationManager, RedisTemplate<String, Object> redisTemplate) {
        LoginFilter loginFilter = new LoginFilter(redisTemplate, authenticationManager);
        loginFilter.setUsernameParameter(SecurityConstant.USERNAME);
        loginFilter.setPasswordParameter(SecurityConstant.PASSWORD);
        loginFilter.setFilterProcessesUrl(SecurityConstant.LOGIN_URL);
        return loginFilter;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public AccreditFilter accreditFilter(RedisTemplate<String, Object> redisTemplate) {
        return new AccreditFilter(redisTemplate);
    }

    /**
     * 强制使用 BCrypt 做密码加密
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public MethodInvokingFactoryBean securityContextHolderMethodInvokingFactoryBean() {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
        methodInvokingFactoryBean.setTargetMethod("setStrategyName");
        methodInvokingFactoryBean.setArguments(TransmittableThreadLocalSecurityContextHolderStrategy.class.getName());
        return methodInvokingFactoryBean;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.addFilterAt(loginFilter(http.getSharedObject(AuthenticationManager.class), redisTemplate),
                UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(accreditFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class);
        http.userDetailsService(userDetailsService);
        http.authorizeRequests()
                .regexMatchers("\\/druid.*").permitAll()
                .antMatchers(SecurityConstant.NO_AUTHORIZE_URL_LIST).permitAll()
                .anyRequest().authenticated()
                .and().formLogin()
                // 异常处理
                .and().exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> {
                    log.info("securityFilterChain authException {}", authException.getMessage());
                    ResponseUtils.out(response, ResponseResult.error(ResultEnum.LOGIN_NOT_MSG_FAILED));
                })
                //退出设置
                .and().logout()
                .logoutUrl(SecurityConstant.LOGOUT_URL)
                //注销处理器
                .addLogoutHandler(new TokenLogoutHandler(redisTemplate))
                .logoutSuccessHandler((request, response, authentication) -> {
                    ResponseUtils.out(response, ResponseResult.success("注销成功"));
                })
                //跨域
                .and().cors()
                .configurationSource(configurationSource())
                .and().csrf().disable();
        // 关闭默认登录页面
        http.removeConfigurer(DefaultLoginPageConfigurer.class);
        //关闭Session认证
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

    CorsConfigurationSource configurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        corsConfiguration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }


}
